/*
 * Copyright (c) 2009, Hamish Morgan
 * All rights reserver.
 */
package locusts.common.behaviours;

import java.awt.geom.Rectangle2D;

import javax.vecmath.Vector2d;

import locusts.common.entities.Vehicle;

/**
 * Steering to stay withing a rectangular boundry area, or return if
 * the boundry is crossed.
 * 
 * @author Hamish Morgan
 */
public class BoundryAvoidBehaviour extends AbstractBehaviour {
    
    private static final double DEFAULT_AVOID_REPULSION_FORCE = 100d;
    protected Rectangle2D boundry;
    protected double left;
    protected double top;
    protected double right;
    protected double bottom;
    protected double repulsionForce;
    
    public BoundryAvoidBehaviour(double weight, Rectangle2D boundry,
            double repulsionForce) {
        super(weight);
        setBoundry(boundry);
        setRepulsionForce(repulsionForce);
    }
    
    public BoundryAvoidBehaviour(double weight, Rectangle2D boundry) {
        this(weight, boundry, DEFAULT_AVOID_REPULSION_FORCE);
    }
    
    public void setBoundry(Rectangle2D boundry) {
        if (Double.isNaN(boundry.getX()) || Double.isNaN(boundry.getY())
                || Double.isNaN(boundry.getWidth())
                || Double.isNaN(boundry.getHeight())
                || Double.isInfinite(boundry.getX())
                || Double.isInfinite(boundry.getY())
                || Double.isInfinite(boundry.getWidth())
                || Double.isInfinite(boundry.getHeight())) {
            throw new IllegalArgumentException(
                    "Parameter boundry has infinite or NaN values. Using "
                            + "Double.MAX_VALUE or Double.MIN_VALUE "
                            + "unbounded sides.");
        }
        this.boundry = boundry;
        left = boundry.getMinX();
        top = boundry.getMinY();
        right = boundry.getMaxX();
        bottom = boundry.getMaxY();
    }
    
    public void setRepulsionForce(double repulsionForce) {
        if (Double.isNaN(repulsionForce) || Double.isInfinite(repulsionForce)) {
            throw new IllegalArgumentException(
                    "Parameter repulsionForce must be a finite, real number.");
        }
        this.repulsionForce = repulsionForce;
    }
    
    // Attempt to avoid collision with the outside of a rectangular bounding
    // area. Steer away from all walls in inverse proportion to the distance to
    // it, and head back if the wall is crossed.
    public Vector2d getSteering(final Vehicle boid) {
        
        final Vector2d steering = new Vector2d();
        if (getWeight() == 0d)
            return steering;
        
        if (boid.position.x < left)
            steering.x = repulsionForce * (left - boid.position.x);
        else if (boid.position.x > right)
            steering.x = repulsionForce * (right - boid.position.x);
        else if (boid.position.x == left || boid.position.x == right)
            steering.x = 0d;
        else {
            steering.x += repulsionForce / (boid.position.x - left);
            steering.x -= repulsionForce / (right - boid.position.x);
        }
        
        if (boid.position.y < top)
            steering.y = repulsionForce * (top - boid.position.y);
        else if (boid.position.y > bottom)
            steering.y = repulsionForce * (bottom - boid.position.y);
        else if (boid.position.y == top || boid.position.y == bottom)
            steering.y = 0d;
        else {
            steering.y += repulsionForce / (boid.position.y - top);
            steering.y -= repulsionForce / (bottom - boid.position.y);
        }
        
        // checker steering does not exceed the repulsion force (for distances <
        // 1)
        if (steering.lengthSquared() > repulsionForce * repulsionForce) {
            steering.scale(repulsionForce * repulsionForce
                    / steering.lengthSquared());
        }
        steering.scale(getWeight());
        return steering;
    }
}